package org.xserver.component.core;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.Security;

import javax.annotation.Resource;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.xserver.common.util.StringUtil;
import org.xserver.component.config.XServerHttpConfig;

/**
 * Https context factory.
 * 
 * @since 2016/12/20
 * @author idoldefine
 */
@Component
public class XServerHttpSSLEngineFactory {
	@Resource
	private XServerHttpConfig xserverHttpConfig;
	private static final Logger logger = LoggerFactory
			.getLogger(XServerHttpSSLEngineFactory.class);
	private static final String PROTOCOL = "SSLV3";
	private static final String DEFAULT_ALGORITHM = "SunX509";
	private static SSLContext xServerSSLContext;

	private static InputStream STREAM_KEYSTORE;
	private static InputStream STREAM_TRUSTKEYSTORE;

	private static char[] CERTIFICATE_PASSWORD;
	private static char[] KEYSTORE_PASSWORD;

	/**
	 * get keystore stream from the classpath
	 *
	 * @return
	 */
	private InputStream getKeyStoreStream() {
		InputStream inputStream = null;
		try {
			inputStream = this.getClass().getClassLoader()
					.getResourceAsStream(xserverHttpConfig.getKeyStoreDir());
		} catch (Exception e) {
			logger.error("read key store error", e);
		}

		return inputStream;
	}

	/**
	 * get trust keystore stream from the classpath
	 *
	 * @return
	 */
	private InputStream getTrustKeyStoreStream() {
		InputStream inputStream = null;
		try {
			inputStream = this
					.getClass()
					.getClassLoader()
					.getResourceAsStream(
							xserverHttpConfig.getTrustKeyStoreDir());
		} catch (Exception e) {
			logger.error("read trust key store error", e);
		}

		return inputStream;
	}

	/**
	 * get certificate password
	 */
	private char[] getCertificatePassword() {
		char[] certPwd = null;
		try {
			certPwd = xserverHttpConfig.getCertificatePassword().toCharArray();
		} catch (NullPointerException e) {
			logger.error("get certificate password error", e);
		}

		return certPwd;
	}

	private char[] getKeyStorePassword() {
		char[] keyStorePwd = null;
		try {
			keyStorePwd = xserverHttpConfig.getKeystorePassword().toCharArray();
		} catch (NullPointerException e) {
			logger.error("get key store password error", e);
		}

		return keyStorePwd;
	}

	/**
	 * TODO
	 * 
	 * <pre>
	 * <ul>
	 * <li>1. </li>
	 * <li>2. </li>
	 * <li>3. </li>
	 * </ul>
	 * 
	 * <pre>
	 */
	private void init() {
		//get xserver.keystore inputStream
		STREAM_KEYSTORE = getKeyStoreStream();
		if (STREAM_KEYSTORE == null) {
			throw new Error(
					"Failed to initialize the server SSLContext, load keystore error");
		};
		//password - the password for recovering keys in the KeyStore 
		CERTIFICATE_PASSWORD = getCertificatePassword();
		if (CERTIFICATE_PASSWORD == null) {
			throw new Error(
					"Failed to initialize the server SSLContext, load certificate password error");
		}
		//the password used to check the integrity of the keystore, the password used to unlock the keystore, or null
		KEYSTORE_PASSWORD = getKeyStorePassword();
		if (KEYSTORE_PASSWORD == null) {
			throw new Error(
					"Failed to initialize the server SSLContext, load keystore password error");
		}
		//the input stream from which the keystore is loaded, or null
		STREAM_TRUSTKEYSTORE = getTrustKeyStoreStream();
		if (STREAM_TRUSTKEYSTORE == null) {
			throw new Error(
					"Failed to initialize the server SSLContext, load trustKeyStore error");
		}
		
		String algorithm = Security
				.getProperty("ssl.KeyManagerFactory.algorithm");
		if (StringUtil.isEmpty(algorithm)) {
			algorithm = DEFAULT_ALGORITHM;
		}

		SSLContext sslContext = null;

		try {
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(STREAM_KEYSTORE, KEYSTORE_PASSWORD);

			KeyManagerFactory keyManagerFactory = KeyManagerFactory
					.getInstance(algorithm);
			keyManagerFactory.init(ks, CERTIFICATE_PASSWORD);

			sslContext = SSLContext.getInstance(PROTOCOL);

			TrustManagerFactory tmf = null;
			TrustManager[] trustManagers = null;

			KeyStore trustKs = KeyStore.getInstance("JKS");

			trustKs.load(STREAM_TRUSTKEYSTORE, KEYSTORE_PASSWORD);

			tmf = TrustManagerFactory.getInstance(algorithm);
			tmf.init(trustKs);
			trustManagers = tmf.getTrustManagers();

			sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers,
					null);
		} catch (Exception e) {
			logger.error("initialize SSLContext error", e);
			throw new Error("Failed to initialize the server SSLContext", e);
		} finally {
			xServerSSLContext = sslContext;

			try {
				STREAM_KEYSTORE.close();
				STREAM_KEYSTORE = null;
				STREAM_TRUSTKEYSTORE.close();
				STREAM_TRUSTKEYSTORE = null;
			} catch (Exception e) {
				logger.error("initialize SSLContext error", e);
			}
		}
	}

	public SSLEngine getInstance() {
		init();
		SSLEngine sslEngine = xServerSSLContext.createSSLEngine();
		sslEngine.setUseClientMode(false);
		sslEngine.setNeedClientAuth(true);

		return sslEngine;
	}
}
